﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Aga.GestionDeArchivos.Storage;
using Aga.GestionDeArchivos.Domain.Entities;
using AutoMapper;
using System.Transactions;
using Aga.GestionDeArchivos.Common;

namespace Aga.GestionDeArchivos.Services
{
    public class GuardaService : ServiceBase
    {
        public const int MaxCantidadRemitos = Int16.MaxValue;

        /// <summary>
        /// Ingresars the caja.
        /// </summary>
        /// <param name="codigoBarraCaja">The codigo barra caja.</param>
        /// <param name="codigoUbicacion">The codigo ubicacion.</param>
        public Movimiento IngresarCaja(string codigoBarraCaja, string codigoUbicacion)
        {
            string codCaja = codigoBarraCaja;
            string codUbic = codigoUbicacion;
            int numero;
            bool numParsed = Int32.TryParse(codigoUbicacion, out numero);

            codigoBarraCaja = NormalizarCodigoBarra(codigoBarraCaja);
            codigoUbicacion = NormalizarCodigoBarra(codigoUbicacion);

            CajaCliente caja = RepositoryFactory.CreateCajaClienteRepository().GetByCodigoBarra(codigoBarraCaja);

            if (caja == null)
                throw new InvalidOperationException("No se encontró la caja con código " + codCaja);

            Ubicacion ubicacion = RepositoryFactory.CreateUbicacionRepository().GetByCodigo(codigoUbicacion);

            if (ubicacion == null && numParsed)
                ubicacion = RepositoryFactory.CreateUbicacionRepository().GetByNumero(numero);

            if (ubicacion == null)
                throw new InvalidOperationException("No se encontró la ubicacion con el código o número: " + codUbic);

            if (caja.Ubicacion != null)
                throw new InvalidOperationException(String.Format("La caja {0} del cliente {1} ya se encuentra en la ubicación {2}.", caja.Numero, caja.ClienteId, caja.Ubicacion.NumeroCaja));

            if (ubicacion.CajaCliente != null)
                throw new InvalidOperationException(String.Format("La ubicación {0} ya se encuentra ocupada con otra caja.", ubicacion.NumeroCaja));

            var movimiento = new Movimiento
            {
                CajaCliente = caja,
                UbicacionDestinoId = ubicacion.Id,
                UbicacionDestinoNumero = ubicacion.NumeroCaja,
                CreationDate = DateTime.Now,
                ModificationDate = DateTime.Now,
                CreationUser = GetCurrentUser(),
                ModificationUser = GetCurrentUser()
            };

            caja.Ubicacion = ubicacion;
            caja.Estado = EstadoCaja.EnGuarda.GetName();
            caja.Movimientos.Add(movimiento);

            ubicacion.CajaCliente = caja.Id;

            RepositoryFactory.CreateCajaClienteRepository().Save(caja);

            return movimiento;
        }

        /// <summary>
        /// Egresars the caja.
        /// </summary>
        /// <param name="codigoBarraCaja">The codigo barra caja.</param>
        /// <param name="codigoUbicacion">The codigo ubicacion.</param>
        /// <param name="motivo">The motivo.</param>
        /// <param name="solicitante">The solicitante.</param>
        /// <param name="observaciones">The observaciones.</param>
        public Movimiento EgresarCaja(string codigoBarraCaja, string codigoUbicacion, string motivo, string solicitante, string observaciones, MotivoEgreso motivoEgreso = MotivoEgreso.SalidaConsulta)
        {
            string codCaja = codigoBarraCaja;
            string codUbic = codigoUbicacion;
            int numero;
            bool numParsed = Int32.TryParse(codigoUbicacion, out numero);

            codigoBarraCaja = NormalizarCodigoBarra(codigoBarraCaja);
            codigoUbicacion = NormalizarCodigoBarra(codigoUbicacion);

            CajaCliente caja = RepositoryFactory.CreateCajaClienteRepository().GetByCodigoBarra(codigoBarraCaja);

            if (caja == null)
                throw new InvalidOperationException("No se encontró la caja con código " + codCaja);

            Ubicacion ubicacion = RepositoryFactory.CreateUbicacionRepository().GetByCodigo(codigoUbicacion);

            if (ubicacion == null && numParsed)
                ubicacion = RepositoryFactory.CreateUbicacionRepository().GetByNumero(numero);

            if (ubicacion == null)
                throw new InvalidOperationException("No se encontró la ubicacion con el código o número: " + codUbic);

            if (caja.Ubicacion == null)
            {
                throw new InvalidOperationException(String.Format("La caja {0} del cliente {1} no tiene ninguna ubicación asignada.", caja.Numero, caja.ClienteId));
            }
            else if (caja.Ubicacion != ubicacion)
            {
                throw new InvalidOperationException(String.Format("La caja {0} del cliente {1} tiene asignada la ubicación {2}.", caja.Numero, caja.ClienteId, caja.Ubicacion.NumeroCaja));
            }

            if (ubicacion.CajaCliente == null)
                throw new InvalidOperationException(String.Format("La ubicación {0} no contiene ninguna caja.", ubicacion.NumeroCaja));

            if (ubicacion.CajaCliente != caja.Id)
                throw new InvalidOperationException(String.Format("La ubicación {0} no contiene a la caja {1}.", ubicacion.NumeroCaja, codigoBarraCaja));

            caja.Ubicacion = null;
            caja.Estado = motivoEgreso == MotivoEgreso.SalidaConsulta ? EstadoCaja.EnConsulta.GetName() : EstadoCaja.Expurgada.GetName();
            ubicacion.CajaCliente = null;

            var movimiento = new Movimiento
            {
                CajaCliente = caja,
                UbicacionOrigenId = ubicacion.Id,
                UbicacionOrigenNumero = ubicacion.NumeroCaja,
                Motivo = motivo,
                Solicitante = solicitante,
                Observaciones = observaciones,
                CreationDate = DateTime.Now,
                ModificationDate = DateTime.Now,
                CreationUser = GetCurrentUser(),
                ModificationUser = GetCurrentUser()
            };

            caja.Movimientos.Add(movimiento);

            RepositoryFactory.CreateCajaClienteRepository().Save(caja);

            return movimiento;

        }

        /// <summary>
        /// Mueve una caja de una ubicacion a otra.
        /// </summary>
        /// <param name="codigoBarraCaja">The codigo barra caja.</param>
        /// <param name="codigoUbicacionOrigen">The codigo ubicacion origen.</param>
        /// <param name="codigoUbicacionDestino">The codigo ubicacion destino.</param>
        /// <param name="motivo">The motivo.</param>
        /// <param name="solicitante">The solicitante.</param>
        /// <param name="observaciones">The observaciones.</param>
        /// <returns></returns>
        public Movimiento MoverCaja(string codigoBarraCaja, string codigoUbicacionOrigen, string codigoUbicacionDestino, string motivo, string solicitante, string observaciones)
        {
            string codCaja = codigoBarraCaja;
            string codUbicOrigen = codigoUbicacionOrigen;
            string codUbicDestino = codigoUbicacionOrigen;
            int numeroOrigen;
            int numeroDestino;

            bool numOrigenParsed = Int32.TryParse(codigoUbicacionOrigen, out numeroOrigen);
            bool numDestinoParsed = Int32.TryParse(codigoUbicacionDestino, out numeroDestino);

            codigoBarraCaja = NormalizarCodigoBarra(codigoBarraCaja);
            codigoUbicacionOrigen = NormalizarCodigoBarra(codigoUbicacionOrigen);
            codigoUbicacionDestino = NormalizarCodigoBarra(codigoUbicacionDestino);

            CajaCliente caja = RepositoryFactory.CreateCajaClienteRepository().GetByCodigoBarra(codigoBarraCaja);

            if (caja == null)
                throw new InvalidOperationException("No se encontró la caja con código " + codCaja);

            Ubicacion ubicacionOrigen = RepositoryFactory.CreateUbicacionRepository().GetByCodigo(codigoUbicacionOrigen);

            if (ubicacionOrigen == null && numOrigenParsed)
                ubicacionOrigen = RepositoryFactory.CreateUbicacionRepository().GetByNumero(numeroOrigen);

            if (ubicacionOrigen == null)
                throw new InvalidOperationException("No se encontró la ubicacion con el código o número" + codUbicOrigen);

            if (caja.Ubicacion == null)
            {
                throw new InvalidOperationException(String.Format("La caja {0} del cliente {1} no tiene ninguna ubicación asignada.", caja.Numero, caja.ClienteId));
            }
            else if (caja.Ubicacion != ubicacionOrigen)
            {
                throw new InvalidOperationException(String.Format("La caja {0} del cliente {1} tiene asignada la ubicación {2}.", caja.Numero, caja.ClienteId, caja.Ubicacion.NumeroCaja));
            }

            if (ubicacionOrigen.CajaCliente == null)
                throw new InvalidOperationException(String.Format("La ubicación {0} no contiene ninguna caja.", ubicacionOrigen.NumeroCaja));

            if (ubicacionOrigen.CajaCliente != caja.Id)
                throw new InvalidOperationException(String.Format("La ubicación {0} no contiene a la caja {1}.", ubicacionOrigen.NumeroCaja, codigoBarraCaja));

            Ubicacion ubicacionDestino = RepositoryFactory.CreateUbicacionRepository().GetByCodigo(codigoUbicacionDestino);

            if (ubicacionDestino == null && numDestinoParsed)
                ubicacionDestino = RepositoryFactory.CreateUbicacionRepository().GetByNumero(numeroDestino);

            if (ubicacionDestino == null)
                throw new InvalidOperationException("No se encontró la ubicacion con el código o número" + codUbicDestino);

            if (ubicacionDestino.CajaCliente != null && ubicacionOrigen != ubicacionDestino)
                throw new InvalidOperationException(String.Format("La ubicación {0} ya se encuentra ocupada con otra caja.", ubicacionDestino.NumeroCaja));

            caja.Ubicacion = ubicacionDestino;
            ubicacionOrigen.CajaCliente = null;
            ubicacionDestino.CajaCliente = caja.Id;

            var movimiento = new Movimiento
            {
                CajaCliente = caja,
                UbicacionOrigenId = ubicacionOrigen.Id,
                UbicacionOrigenNumero = ubicacionOrigen.NumeroCaja,
                UbicacionDestinoId = ubicacionDestino.Id,
                UbicacionDestinoNumero = ubicacionDestino.NumeroCaja,
                Motivo = motivo,
                Solicitante = solicitante,
                Observaciones = observaciones,
                CreationDate = DateTime.Now,
                ModificationDate = DateTime.Now,
                CreationUser = GetCurrentUser(),
                ModificationUser = GetCurrentUser()
            };

            caja.Movimientos.Add(movimiento);

            RepositoryFactory.CreateCajaClienteRepository().Save(caja);

            return movimiento;
        }

        /// <summary>
        /// Buscars the caja by codigo.
        /// </summary>
        /// <param name="codigoBarraCaja">The codigo barra caja.</param>
        /// <returns></returns>
        public CajaCliente BuscarCajaByCodigo(string codigoBarraCaja)
        {
            codigoBarraCaja = NormalizarCodigoBarra(codigoBarraCaja);

            CajaCliente caja = RepositoryFactory.CreateCajaClienteRepository().GetByCodigoBarra(codigoBarraCaja);

            if (caja == null)
                throw new InvalidOperationException("No se encontró la caja con código " + codigoBarraCaja);

            return caja;
        }

        /// <summary>
        /// Busca la caja por el código de ubicacion.
        /// </summary>
        /// <param name="codigoUbicacion">The codigo ubicacion.</param>
        /// <returns></returns>
        public CajaCliente BuscarCajaByUbicacion(string codigoUbicacion)
        {
            string codigo = codigoUbicacion;
            int numero;
            bool numParsed = Int32.TryParse(codigo, out numero);

            codigoUbicacion = NormalizarCodigoBarra(codigoUbicacion);

            Ubicacion ubicacion = RepositoryFactory.CreateUbicacionRepository().GetByCodigo(codigoUbicacion);

            if (ubicacion == null && numParsed)
                ubicacion = RepositoryFactory.CreateUbicacionRepository().GetByNumero(numero);

            if (ubicacion == null)
                throw new InvalidOperationException("No se encontró la ubicacion con el código o número: " + codigo);

            if (ubicacion.Caja == null || ubicacion.Caja.Count == 0)
                throw new InvalidOperationException(String.Format("La ubicación {0} no contiene ninguna caja.", ubicacion.NumeroCaja));

            return ubicacion.Caja.FirstOrDefault();
        }

        /// <summary>
        /// Obtener movimientos.
        /// </summary>
        /// <param name="codigoCaja">The codigo caja.</param>
        /// <returns></returns>
        public IQueryable<Movimiento> ObtenerMovimientos(string codigoCaja)
        {
            CajaCliente caja = RepositoryFactory.CreateCajaClienteRepository().GetByCodigoBarra(codigoCaja);

            if (caja == null)
                throw new InvalidOperationException("No se encontró la caja con código " + codigoCaja);

            return caja.Movimientos.OrderByDescending(m => m.CreationDate).AsQueryable();

        }

        /// <summary>
        /// Salida de Documentacion
        /// </summary>
        /// <param name="clienteId"></param>
        /// <param name="responsable"></param>
        /// <param name="domicilioEntrega"></param>
        /// <param name="observaciones"></param>
        /// <param name="items"></param>
        /// <returns></returns>
        public Remito SalidaDeCaja(DateTime fecha, int clienteId, string responsable, string domicilioEntrega, string observaciones, IEnumerable<NuevoItemRemito> items)
        {
            Remito remitoInicial = null;
            Remito remitoAnterior = null;
            int parcial = 0;

            Cliente cliente = RepositoryFactory.CreateClienteRepository().GetById(clienteId);

            if (cliente == null)
                throw new InvalidOperationException("No se encontró el cliente.");

            List<NuevoItemRemito> itemsAProcesar = new List<NuevoItemRemito>(items.Take(MaxCantidadRemitos));
            List<NuevoItemRemito> itemsPendientes = new List<NuevoItemRemito>(items.Skip(MaxCantidadRemitos));

            using (var scope = new TransactionScope())
            {
                while (itemsAProcesar.Count > 0)
                {
                    parcial++;

                    Remito remitoParcial = new Remito()
                    {
                        Fecha = fecha,
                        ClienteId = clienteId,
                        Responsable = responsable,
                        DomicilioEntrega = domicilioEntrega,
                        Observaciones = observaciones,
                        TipoRemito = (int)TipoRemito.ContenedorCerrado
                    };

                    if (parcial == 1) // Primer remito
                    {
                        remitoParcial.Raiz = null;
                        remitoInicial = remitoParcial;
                    }
                    else // continuaciones
                    {
                        remitoParcial.Observaciones += String.Format("{0}{1}", (String.IsNullOrEmpty(observaciones) ? string.Empty : ". "), String.Format("Continuación del remito N°{0}", remitoAnterior.Numero));
                        remitoParcial.Raiz = remitoAnterior;
                    }

                    List<CajaCliente> cajas = new List<CajaCliente>();

                    foreach (var item in itemsAProcesar)
                    {
                        var codCaja = NormalizarCodigoBarra(item.Caja);

                        CajaCliente caja = RepositoryFactory.CreateCajaClienteRepository().GetByCodigoBarra(codCaja);

                        if (caja.Estado != EstadoCaja.EnGuarda.GetName())
                            throw new InvalidOperationException("La caja no se encuentra en guarda. Estado de la caja: " + caja.Estado);

                        cajas.Add(caja);

                        remitoParcial.Items.Add(new ItemRemito
                        {
                            CajaCliente = caja,
                            Cantidad = item.Cantidad,
                            Remito = remitoParcial,
                            Detalle = item.Detalle ?? string.Empty
                        });
                    }

                    RepositoryFactory.CreateRemitoRepository().Save(remitoParcial);

                    // Actualizo las observaciones.
                    if (itemsPendientes.Count > 0)
                    {
                        remitoParcial.Observaciones += String.Format("{0}{1}", (String.IsNullOrEmpty(remitoParcial.Observaciones) ? string.Empty : ". "), "Continua en remito N°" + (remitoParcial.Numero + 1));
                        RepositoryFactory.CreateRemitoRepository().Save(remitoParcial);
                    }

                    foreach (var caja in cajas)
                    {
                        var item = items.Where(i => NormalizarCodigoBarra(i.Caja) == caja.CodigoBarra).First();

                        caja.Estado = EstadoCaja.EnConsulta.ToString();

                        RepositoryFactory.CreateCajaClienteRepository().Save(caja);

                        // Genero un movimiento interno nulo para que quede como registro
                        MoverCaja(caja.CodigoBarra, caja.Ubicacion.CodigoBarra, caja.Ubicacion.CodigoBarra, MotivoEgreso.SalidaConsulta.ToString(), responsable, String.Format("@Remito:{0}", remitoParcial.Id));
                    }

                    itemsAProcesar = new List<NuevoItemRemito>(itemsPendientes.Take(MaxCantidadRemitos));
                    itemsPendientes = new List<NuevoItemRemito>(itemsPendientes.Skip(MaxCantidadRemitos));
                    remitoAnterior = remitoParcial;
                }

                scope.Complete();
            }

            return remitoInicial;
        }

        /// <summary>
        /// Obteners the remito.
        /// </summary>
        /// <param name="id">The id.</param>
        /// <returns></returns>
        public Remito ObtenerRemito(int id)
        {
            return RepositoryFactory.CreateRemitoRepository().GetById(id);
        }

        /// <summary>
        /// Marca el estado de una caja como "En Guarda" y genera un movimiento interno.
        /// </summary>
        /// <param name="codigoCaja"></param>
        /// <param name="observaciones"></param>
        public void RetornoDeCaja(string codigoCaja, string observaciones)
        {
            CajaCliente caja = RepositoryFactory.CreateCajaClienteRepository().GetByCodigoBarra(codigoCaja);

            if (caja.Estado != EstadoCaja.EnConsulta.GetName())
                throw new InvalidOperationException("La caja no se encuentra en consulta. Estado de la caja: " + caja.Estado);

            using (var scope = new TransactionScope())
            {
                // Genero un movimiento interno nulo para que quede como registro
                MoverCaja(codigoCaja, caja.Ubicacion.CodigoBarra, caja.Ubicacion.CodigoBarra, MotivoEgreso.RetornoConsulta.ToString(), string.Empty, observaciones);

                caja.Estado = EstadoCaja.EnGuarda.GetName();
                RepositoryFactory.CreateCajaClienteRepository().Save(caja);

                scope.Complete();
            }
        }

        /// <summary>
        /// Devuelve todos los remitos
        /// </summary>
        /// <returns></returns>
        public IQueryable<Remito> ObtenerRemitos(string buscar = null)
        {
            buscar = (buscar ?? string.Empty).Trim().ToLower();

            int? numValue = null;
            int val;
            if (Int32.TryParse(buscar, out val))
                numValue = val;

            var remitos = RepositoryFactory.CreateRemitoRepository().GetAll();

            if (!String.IsNullOrWhiteSpace(buscar))
                remitos = remitos.Where(r =>
                    (r.Cliente.Nombre.ToLower().Contains(buscar)) ||
                    (numValue.HasValue && r.ClienteId == numValue) ||
                    (numValue.HasValue && r.Numero == numValue) ||
                    (r.DomicilioEntrega.ToLower().Contains(buscar)) ||
                    (r.Observaciones.ToLower().Contains(buscar)) ||
                    (r.Responsable.ToLower().Contains(buscar)) ||
                    (r.Items.Any(i => i.Descripcion.ToLower().Contains(buscar))) ||
                    (r.Items.Any(i => i.Detalle.ToLower().Contains(buscar))) ||
                    (numValue.HasValue && r.Items.Any(i => i.CajaCliente != null && i.CajaCliente.Numero == numValue)));

            remitos = remitos.OrderByDescending(r => r.CreationDate);

            return remitos;
        }

        /// <summary>
        /// Verifica si la caja pertenece al cliente
        /// </summary>
        /// <param name="p"></param>
        /// <param name="p_2"></param>
        /// <returns></returns>
        public bool EsCajaDelCliente(string codigoCaja, int clienteId)
        {
            CajaCliente caja = RepositoryFactory.CreateCajaClienteRepository().GetByCodigoBarra(codigoCaja);

            if (caja == null)
                return false;

            return (caja.ClienteId == clienteId);
        }

        /// <summary>
        /// Salida de Documentacion
        /// </summary>
        /// <param name="clienteId"></param>
        /// <param name="responsable"></param>
        /// <param name="domicilioEntrega"></param>
        /// <param name="observaciones"></param>
        /// <param name="items"></param>
        /// <returns></returns>
        public Remito SalidaDeDocumentacion(DateTime fecha, int clienteId, string responsable, string domicilioEntrega, string observaciones, IEnumerable<NuevoItemRemito> items)
        {
            Remito remitoInicial = null;
            Remito remitoAnterior = null;
            int parcial = 0;

            Cliente cliente = RepositoryFactory.CreateClienteRepository().GetById(clienteId);

            if (cliente == null)
                throw new InvalidOperationException("No se encontró el cliente.");

            List<NuevoItemRemito> itemsAProcesar = new List<NuevoItemRemito>(items.Take(MaxCantidadRemitos));
            List<NuevoItemRemito> itemsPendientes = new List<NuevoItemRemito>(items.Skip(MaxCantidadRemitos));

            using (var scope = new TransactionScope())
            {
                while (itemsAProcesar.Count > 0)
                {
                    parcial++;

                    Remito remitoParcial = new Remito()
                    {
                        Fecha = fecha,
                        ClienteId = clienteId,
                        Responsable = responsable,
                        DomicilioEntrega = domicilioEntrega,
                        Observaciones = observaciones,
                        TipoRemito = (int)TipoRemito.ContenedorAbierto
                    };

                    if (parcial == 1) // Primer remito
                    {
                        remitoParcial.Raiz = null;
                        remitoInicial = remitoParcial;
                    }
                    else // continuaciones
                    {
                        remitoParcial.Observaciones += String.Format("{0}{1}", (String.IsNullOrEmpty(observaciones) ? string.Empty : ". "), String.Format("Continuación del remito N°{0}", remitoAnterior.Numero));
                        remitoParcial.Raiz = remitoAnterior;
                    }

                    List<CajaCliente> cajas = new List<CajaCliente>();

                    foreach (var item in itemsAProcesar)
                    {
                        CajaCliente caja = RepositoryFactory.CreateCajaClienteRepository().GetByCodigoBarra(item.Caja);

                        cajas.Add(caja);

                        remitoParcial.Items.Add(new ItemRemito
                        {
                            CajaCliente = caja,
                            Cantidad = item.Cantidad,
                            Remito = remitoParcial,
                            Detalle = item.Detalle
                        });
                    }

                    RepositoryFactory.CreateRemitoRepository().Save(remitoParcial);

                    // Actualizo las observaciones.
                    if (itemsPendientes.Count > 0)
                    {
                        remitoParcial.Observaciones += String.Format("{0}{1}", (String.IsNullOrEmpty(remitoParcial.Observaciones) ? string.Empty : ". "), "Continua en remito N°" + (remitoParcial.Numero + 1));
                        RepositoryFactory.CreateRemitoRepository().Save(remitoParcial);
                    }

                    foreach (var caja in cajas)
                    {
                        var item = items.Where(i => NormalizarCodigoBarra(i.Caja) == caja.CodigoBarra).First();

                        // Genero un movimiento interno nulo para que quede como registro
                        MoverCaja(caja.CodigoBarra, caja.Ubicacion.CodigoBarra, caja.Ubicacion.CodigoBarra, MotivoEgreso.SalidaDocumentacion.ToString(), responsable, String.Format("@Remito:{0}", remitoParcial.Id));
                    }
                    itemsAProcesar = new List<NuevoItemRemito>(itemsPendientes.Take(MaxCantidadRemitos));
                    itemsPendientes = new List<NuevoItemRemito>(itemsPendientes.Skip(MaxCantidadRemitos));
                    remitoAnterior = remitoParcial;
                }

                scope.Complete();
            }

            return remitoInicial;
        }

        /// <summary>
        /// Nuevo Remito
        /// </summary>
        /// <param name="clienteId"></param>
        /// <param name="responsable"></param>
        /// <param name="domicilioEntrega"></param>
        /// <param name="observaciones"></param>
        /// <param name="items"></param>
        /// <returns></returns>
        public Remito NuevoRemito(DateTime fecha, int clienteId, string responsable, string domicilioEntrega, string observaciones, IEnumerable<NuevoItemRemito> items)
        {
            Remito remitoInicial = null;
            Remito remitoAnterior = null;
            int parcial = 0;

            Cliente cliente = RepositoryFactory.CreateClienteRepository().GetById(clienteId);

            if (cliente == null)
                throw new InvalidOperationException("No se encontró el cliente.");

            List<NuevoItemRemito> itemsAProcesar = new List<NuevoItemRemito>(items.Take(MaxCantidadRemitos));
            List<NuevoItemRemito> itemsPendientes = new List<NuevoItemRemito>(items.Skip(MaxCantidadRemitos));

            using (var scope = new TransactionScope())
            {
                while (itemsAProcesar.Count > 0)
                {
                    parcial++;

                    Remito remitoParcial = new Remito()
                    {
                        Fecha = fecha,
                        ClienteId = clienteId,
                        Responsable = responsable,
                        DomicilioEntrega = domicilioEntrega,
                        Observaciones = observaciones,
                        TipoRemito = (int)TipoRemito.RemitoVarios
                    };

                    if (parcial == 1) // Primer remito
                    {
                        remitoParcial.Raiz = null;
                        remitoInicial = remitoParcial;
                    }
                    else // continuaciones
                    {
                        remitoParcial.Observaciones += String.Format("{0}{1}", (String.IsNullOrEmpty(observaciones) ? string.Empty : ". "), String.Format("Continuación del remito N°{0}", remitoAnterior.Numero));
                        remitoParcial.Raiz = remitoAnterior;
                    }

                    foreach (var item in itemsAProcesar)
                    {
                        Item i = RepositoryFactory.CreateItemRepository().GetById(item.ItemId.Value);

                        if (i == null)
                            throw new InvalidOperationException("No se encontró el item con id = " + item.ItemId.Value);

                        remitoParcial.Items.Add(new ItemRemito
                        {
                            Item = i,
                            Cantidad = item.Cantidad,
                            Remito = remitoParcial,
                            Detalle = i.Detalle,
                            Descripcion = item.Descripcion
                        });
                    }

                    RepositoryFactory.CreateRemitoRepository().Save(remitoParcial);

                    // Actualizo las observaciones.
                    if (itemsPendientes.Count > 0)
                    {
                        remitoParcial.Observaciones += String.Format("{0}{1}", (String.IsNullOrEmpty(remitoParcial.Observaciones) ? string.Empty : ". "), "Continua en remito N°" + (remitoParcial.Numero + 1));
                        RepositoryFactory.CreateRemitoRepository().Save(remitoParcial);
                    }

                    itemsAProcesar = new List<NuevoItemRemito>(itemsPendientes.Take(MaxCantidadRemitos));
                    itemsPendientes = new List<NuevoItemRemito>(itemsPendientes.Skip(MaxCantidadRemitos));
                    remitoAnterior = remitoParcial;
                }

                scope.Complete();
            }

            return remitoInicial;
        }

        /// <summary>
        /// Obtiene los items de remitos de salida de documentacion que no han sido retornados.
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        public RetornoDocumentacion ObtenerDocumentacionSinRetornar(int clienteId, int pageIndex, int pageSize)
        {
            var nombreCliente = RepositoryFactory.CreateClienteRepository().GetAll().Where(c => c.Id == clienteId).Select(c => c.Nombre).FirstOrDefault();

            RetornoDocumentacion dto = new RetornoDocumentacion()
            {
                ClienteId = clienteId,
                NombreCliente = nombreCliente
            };

            var items = RepositoryFactory.CreateRemitoRepository()
                .GetAll()
                .Where(r => r.ClienteId == clienteId && r.TipoRemito == (int)TipoRemito.ContenedorAbierto)
                .SelectMany(r => r.Items)
                .Where(i => !i.Retornado)
                .Select(i => new ItemRetornoDocumentacion
                {
                    Id = i.Id,
                    NumeroRemito = i.RemitoId,
                    FechaRemito = i.Remito.Fecha,
                    Cantidad = i.Cantidad,
                    CajaCliente = i.CajaCliente.Numero,
                    Descripcion = i.Descripcion,
                    Detalle = i.Detalle
                });

            int totalCount = items.Count();
            var pageItems = items.OrderBy(c => c.FechaRemito).Skip(pageIndex * pageSize).Take(pageSize).ToList();
            int pageCount = Convert.ToInt32(Math.Ceiling((double)totalCount / pageSize));

            dto.Items = new PagedList<ItemRetornoDocumentacion>(pageItems, pageIndex, pageCount, pageSize, totalCount);

            return dto;
        }

        private static string NormalizarCodigoBarra(string codigoBarra)
        {
            if (!codigoBarra.ToCharArray().Contains('-'))
            {
                if (!codigoBarra.StartsWith("*"))
                    codigoBarra = "*" + codigoBarra;
                if (!codigoBarra.EndsWith("*"))
                    codigoBarra += "*";
            }
            return codigoBarra;
        }

        public void RetornoDeDocumentacion(int itemId, string observaciones)
        {
            ItemRemito item = RepositoryFactory.CreateRemitoRepository().GetAll()
                .Where(r => r.Items.Any(i => i.Id == itemId))
                .SelectMany(r => r.Items)
                .FirstOrDefault(i => i.Id == itemId);

            item.Retornado = true;
            item.ModificationDate = DateTime.Now;
            item.ModificationUser = GetCurrentUser();
            item.ObservacionesRetorno = observaciones;

            RepositoryFactory.CreateRemitoRepository().Save(item.Remito);
        }

        public PagedList<Remito> GetPage(int pageIndex, int pageSize)
        {
            var remitos = RepositoryFactory.CreateRemitoRepository().GetAll();

            return GetPage(remitos, pageIndex, pageSize);
        }

        public PagedList<Remito> GetPage(IQueryable<Remito> remitos, int pageIndex, int pageSize)
        {
            int totalCount = remitos.Count();

            var items = remitos.OrderBy(c => c.Numero).Skip(pageIndex * pageSize).Take(pageSize).ToList();

            int pageCount = Convert.ToInt32(Math.Ceiling((double)totalCount / pageSize));

            return new PagedList<Remito>(items, pageIndex, pageCount, pageSize, totalCount);
        }
    }

    public enum MotivoEgreso
    {
        SalidaConsulta,
        RetornoConsulta,
        Expurgo,
        SalidaDocumentacion
    }
}
